#include "MultiPopCore.hpp"

#define MULTIPOPCORE_MAX_CORES  256
#define __MULTIPOPCORE_DEBUG__
#define __MPC_GENTIME_FILE__    "mpc_Gtime.txt"

MultiPopCore::MultiPopCore(Validator *val,GAmodSettings &settings) throw (MultiPopCoreException)
: GAcore(val,settings)
{
    this->myCores = NULL;
    //this->myVal = val;
    this->n_cores=0;
    this->IB_timing = IB_TIMING_NONE;
    this->IB_type = IB_TYPE_NONE;

    try
    {
        this->loadSettings(settings,GAcore::THROW_EXCEPTION);
    }
    catch(MultiPopCoreException &se)
    {
        std::string errmsg("MultiPopCore::MultiPopCore:\n\t");
        errmsg = errmsg + se.msg;
        throw MultiPopCoreException((char*)errmsg.c_str());
    }
}

int MultiPopCore::loadSettings(GAmodSettings &settings,load_exc le) throw(MultiPopCoreException)
{
	TemplateGAmodObjWrapper<string> *strsett;
	const char *tmp;
    int p_size=0,i;

    //strsett = (GAmodStrSetting*)settings.get_setting("num_cores");
    //tmp = (strsett) ? ((GAmodStrSetting*)strsett)->toStr() : NULL;

    strsett = (TemplateGAmodObjWrapper<string>*)settings.get_setting("num_cores");
    tmp = (strsett) ? strsett->value.c_str() : NULL;

    if(tmp)
    {
        p_size = atoi(tmp);

        IF_DBG_VERB(printf("[INFO] MultiPopCore::loadSettings: NUM_CORES <%s> = %d\n",tmp,p_size);)

        if(p_size<=0 || p_size>MULTIPOPCORE_MAX_CORES)
            throw MultiPopCoreException("MultiPopCore::loadSettings: num_cores invalido");

        if(this->myCores)
            deleteCores();


        this->myCores = (SimpleCore**) malloc(sizeof(SimpleCore*) * p_size);
        for(i=0; i<p_size; i++)
        {
            try
            {
                this->myCores[i] = new SimpleCore(this->myValidator,settings);
                this->n_cores++;

            }
            catch(SimpleCoreException &e)
            {
                //Elimino i cores eventualmente creati
                deleteCores();
                std::string errmsg("MultiPopCore::loadSettings:\n\t");
                errmsg = errmsg + e.msg;
                throw MultiPopCoreException((char*)errmsg.c_str());
            }
        }


    }
    else
        if(le==GAcore::THROW_EXCEPTION)
            throw MultiPopCoreException("MultiPopCore::loadSettings: num_cores non specificato.");

    //strsett = (GAmodStrSetting*)settings.get_setting("IB_type");
    //tmp = (strsett) ? ((GAmodStrSetting*)strsett)->toStr() : NULL;

    strsett = (TemplateGAmodObjWrapper<string>*)settings.get_setting("IB_type");
    tmp = (strsett) ? strsett->value.c_str() : NULL;

    if(tmp)
    {
        IF_DBG_VERB(printf("[INFO] MultiPopCore::loadSettings: IB_TYPE <%s>\n",tmp);)
        if(strcmp(tmp,"BW_replace")==0)
        {
            IB_type = IB_TYPE_BW_REPLACE;

            strsett = (TemplateGAmodObjWrapper<string>*)settings.get_setting("IB_parm_BperCore");
            tmp = (strsett) ? strsett->value.c_str() : NULL;

            if(tmp)
            {
                this->BperCore = atoi(tmp);
                IF_DBG_VERB(printf("[INFO] MultiPopCore::loadSettings: IB_parm_BperCore <%s> = %d\n",tmp,BperCore);)

                //TODO: controllare compatibilita' con dimensione popolazioni
                if(this->BperCore<=0)
                    throw MultiPopCoreException("MultiPopCore::loadSettings: IB_parm_BperCore non valido.");
            }
            else
                if(le==GAcore::THROW_EXCEPTION)
                    throw MultiPopCoreException("MultiPopCore::loadSettings: IB_parm_BperCore  non specificato.");
        }
        else
            throw MultiPopCoreException("MultiPopCore::loadSettings: IB_type non valido.");

    }
    else
    {
        IF_DBG_INFO(printf("[WARN] MultiPopCore::loadSettings: IB_type non specificato, IB_type=IB_TYPE_NONE\n");)
        this->IB_type = IB_TYPE_NONE;
    }

    strsett = (TemplateGAmodObjWrapper<string>*)settings.get_setting("IB_timing");
    tmp = (strsett) ? strsett->value.c_str() : NULL;

    if(tmp)
    {
        IF_DBG_VERB(printf("[INFO] MultiPopCore::loadSettings: IB_timing <%s>\n",tmp);)
        if(strcmp(tmp,"const_gens")==0)
        {
            this->IB_timing = IB_TIMING_CONST;

            strsett = (TemplateGAmodObjWrapper<string>*)settings.get_setting("IB_parm_numGens");
            tmp = (strsett) ? strsett->value.c_str() : NULL;
            if(tmp)
            {
                this->numGens = atoi(tmp);
                IF_DBG_VERB(printf("[INFO] MultiPopCore::loadSettings: IB_parm_numGens <%s> = %d\n",tmp,this->numGens);)

                if(this->numGens<=0)
                    throw MultiPopCoreException("MultiPopCore::loadSettings: numGens non valido.");
            }
            else
                if(le==GAcore::THROW_EXCEPTION)
                    throw MultiPopCoreException("MultiPopCore::loadSettings: IB_parm_numGens non specificato.");

        }
        else
            throw MultiPopCoreException("MultiPopCore::loadSettings: IB_timing non valido.");
    }
    else
    {
        if(le==GAcore::THROW_EXCEPTION)
            throw MultiPopCoreException("MultiPopCore::loadSettings: IB_timing non specificato.");
    }

    return this->n_cores;
}

int MultiPopCore::runGeneration(int num)
{
    int n,i,j;
#ifdef __MULTIPOPCORE_DEBUG__
    static FILE *mpc_tdbg_file=NULL;
    clock_t t0,t1;

    if(mpc_tdbg_file==NULL) mpc_tdbg_file=fopen(__MPC_GENTIME_FILE__,"w");
#endif


    for(n=0,i=0; n<num; n++, i++)
    {
        IF_DBG_VERB(printf("*"); fflush(stdout);)

#ifdef __MULTIPOPCORE_DEBUG__
        t0=clock();
#endif

        for(j=0; j<this->n_cores; j++)
            this->myCores[j]->runGeneration(1);

        if( check_IB_timing() )
            apply_IB();

#ifdef __MULTIPOPCORE_DEBUG__
        t1=clock();
        fprintf(mpc_tdbg_file,"%f\n",(t1-t0)/(float)(CLOCKS_PER_SEC));
        fflush(mpc_tdbg_file);
#endif

    }

    //this->numCrossover = this->numCrossover / (float)num;
    //this->numMut = this->numMut / (num * M->get_size());

    //printf("NC = %f, NM = %f\n",numCrossover,numMut);
    //Individuo b;
    //b = M->getBestIndividuo();
    //printf("best = %d",b.fitness);

    return i++;
}

int MultiPopCore::getGenerationData(GAmodSettings &settings,HERAuint32 lista)
{
    int np=0,i;
    float max;
    char strtmp[512];
    GAmodSettings local_set,*remote_set;
    GAmodObjWrapper *objval;
    GAmodFloatSetting *strsett;

    settings.clear();
    remote_set = new GAmodSettings[this->n_cores] ();

    for(i=0; i<this->n_cores; i++)
        this->myCores[i]->getGenerationData(remote_set[i],lista);

    if(lista==0) lista = ~lista;

    if(lista & MultiPopCore::PARAM_BEST_SCORE)
    {
        max = 0;
        for(i=0; i<this->n_cores; i++)
        {
            strsett = (GAmodFloatSetting*)remote_set[i].get_setting("best_score");
            if(strsett)
                if(max<strsett->val) max = strsett->val;
        }

        objval = new GAmodFloatSetting(max);
        settings.add_setting("best_score",objval);
        np++;
    }

    if(lista & MultiPopCore::PARAM_ALL_B_SCORES)
    {
        for(i=0; i<this->n_cores; i++)
        {
            strsett = (GAmodFloatSetting*)remote_set[i].get_setting("best_score");
            if(strsett)
            {
                objval = new GAmodFloatSetting(strsett->val);
                sprintf(strtmp,"best_score_%d",i);
                settings.add_setting(strtmp,objval);
                np++;
            }
        }
    }

    if(lista & MultiPopCore::PARAM_ALL_MEAN)
    {
        for(i=0; i<this->n_cores; i++)
        {
            strsett = (GAmodFloatSetting*)remote_set[i].get_setting("mean_score");
            if(strsett)
            {
                objval = new GAmodFloatSetting(strsett->val);
                sprintf(strtmp,"mean_score_%d",i);
                settings.add_setting(strtmp,objval);
                np++;
            }
        }
    }

    if(lista & MultiPopCore::PARAM_ALL_HAM_BEST)
    {
        for(i=0; i<this->n_cores; i++)
        {
            strsett = (GAmodFloatSetting*)remote_set[i].get_setting("ham_best");
            if(strsett)
            {
                objval = new GAmodFloatSetting(strsett->val);
                sprintf(strtmp,"ham_best_%d",i);
                settings.add_setting(strtmp,objval);
                np++;
            }
        }
    }

    //TODO: implementare calcolo VAR_SCORE per multipop
    if(lista & MultiPopCore::PARAM_MEAN_SCORE || lista & MultiPopCore::PARAM_VAR_SCORE)
    {
        //sprintf(str,"%.4f",M->mean_fitness(&a));
        max = 0;
        for(i=0; i<this->n_cores; i++)
        {
            strsett = (GAmodFloatSetting*)remote_set[i].get_setting("mean_score");
            if(strsett)
                 max += strsett->val;
        }

        max = max / this->n_cores;

        objval = new GAmodFloatSetting(max);
        settings.add_setting("mean_score",objval);
        //sprintf(str,"%.4f",a);
        np++;
    }


    if(lista & MultiPopCore::PARAM_NUM_CORES )
    {
        objval = new GAmodFloatSetting(this->n_cores);
        settings.add_setting("num_cores",objval);
        np++;
    }

    delete[] remote_set;

    return np;
}

void MultiPopCore::deleteCores()
{
    int i;

    //printf("Deleting %d cores...\n",this->n_cores);

    for(i=0; i<this->n_cores; i++)
        delete this->myCores[i];

    free(this->myCores);
    this->myCores=NULL;

    this->n_cores=0;
}

int MultiPopCore::check_IB_timing()
{
    static int n_gens = 0;

    switch(this->IB_timing)
    {
        case IB_TIMING_NONE: break;;
        case IB_TIMING_CONST:
        {
            n_gens++;
            if(n_gens == this->numGens)
            {
                n_gens = 0 ;
                return 1;
            }
            break;
        }
    }

    return 0;
}

void MultiPopCore::apply_IB()
{
    switch(this->IB_type)
    {
        case IB_TYPE_NONE: break;
        case IB_TYPE_BW_REPLACE: IB_replace(); break;
    }
}

void MultiPopCore::IB_replace()
{
    int i,j,k,l;

    for(i=0; i<this->n_cores; i++)
    {
        this->myCores[i]->M->sort_best_individual(this->BperCore);
        this->myCores[i]->M->sort_worse_individual(this->BperCore*(this->n_cores-1));
    }


    //Sostituisco i peggiori this->BperCore*(this->n_cores-1) individui con i migliori this->BperCore provenienti dagli altri cores
    for(i=0; i<this->n_cores; i++)
    {
        k=this->myCores[i]->M->get_size()-1;

        for(j=0; j<this->n_cores; j++)
        {
            if(j==i) continue;
            for(l=0; l<this->BperCore; l++)
            {
                //IF_DBG_VERB(printf("MultiPopCore::IB_replace [%d][%d] <- [%d][%d]",i,k,j,l);)
                (*this->myCores[i]->M)[k--] = (*this->myCores[j]->M)[l];
            }
        }
    }

    printf("\n -- IB REPLACE -- \n");
}

void MultiPopCore::getPopolazione(Popolazione &p)
{
	int i,imax=0,max;
	max = 0;
	GAmodFloatSetting *flt;
	GAmodSettings *remote_set;

	remote_set = new GAmodSettings[this->n_cores] ();

    for(i=0; i<this->n_cores; i++)
        this->myCores[i]->getGenerationData(remote_set[i],SimpleCore::PARAM_BEST_SCORE);

	for(i=0; i<this->n_cores; i++)
	{
		flt = (GAmodFloatSetting*)remote_set[i].get_setting("best_score");
		if(flt)
			if(max<flt->val)
			{
				max = flt->val;
				imax = i;
			}
	}

	myCores[imax]->getPopolazione(p);

	return;
}
